package com.linln.admin.system.controller;

import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.ImmutableMap;
import com.linln.admin.system.domain.Schedule;
import com.linln.admin.system.service.ScheduleService;
import com.linln.admin.system.service.ql.RuleComponent;
import com.linln.admin.system.validator.ScheduleValid;
import com.linln.common.enums.StatusEnum;
import com.linln.common.utils.EntityBeanUtil;
import com.linln.common.utils.ResultVoUtil;
import com.linln.common.utils.StatusUtil;
import com.linln.common.vo.ResultVo;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.ExampleMatcher;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author taofucheng
 * @date 2022/09/03
 */
@Slf4j
@Controller
@RequestMapping("/system/schedule")
public class ScheduleController {

    @Autowired
    private ScheduleService scheduleService;
    @Resource
    private RuleComponent ruleComponent;

    /**
     * 列表页面
     */
    @GetMapping("/index")
    @RequiresPermissions("system:schedule:index")
    public String index(Model model, Schedule schedule) {

        // 创建匹配器，进行动态查询匹配
        ExampleMatcher matcher = ExampleMatcher.matching()
                .withMatcher("title", match -> match.contains())
                .withMatcher("cron", match -> match.contains())
                .withMatcher("status", match -> match.contains());

        // 获取数据列表
        Example<Schedule> example = Example.of(schedule, matcher);
        Page<Schedule> list = scheduleService.getPageList(example);

        // 封装数据
        model.addAttribute("list", list.getContent());
        model.addAttribute("page", list);
        return "/system/schedule/index";
    }

    /**
     * 跳转到添加页面
     */
    @GetMapping("/add")
    @RequiresPermissions("system:schedule:add")
    public String toAdd() {
        return "/system/schedule/add";
    }

    /**
     * 跳转到编辑页面
     */
    @GetMapping("/edit/{id}")
    @RequiresPermissions("system:schedule:edit")
    public String toEdit(@PathVariable("id") Schedule schedule, Model model) {
        model.addAttribute("schedule", schedule);
        return "/system/schedule/add";
    }

    /**
     * 保存添加/修改的数据
     *
     * @param valid 验证对象
     */
    @PostMapping("/save")
    @RequiresPermissions({"system:schedule:add", "system:schedule:edit"})
    @ResponseBody
    public ResultVo save(@Validated ScheduleValid valid, Schedule schedule) {
        // 复制保留无需修改的数据
        if (schedule.getId() != null) {
            Schedule beSchedule = scheduleService.getById(schedule.getId());
            EntityBeanUtil.copyProperties(beSchedule, schedule);
        }

        // 保存数据
        scheduleService.save(schedule);
        return ResultVoUtil.SAVE_SUCCESS;
    }

    /**
     * 运行一下写的脚本
     */
    @PostMapping("/runscript")
    @RequiresPermissions("system:schedule:runscript")
    @ResponseBody
    public ResultVo runScript(Schedule schedule) {
        try {
            Object ret = ruleComponent.execute(ImmutableMap.of("schedule", schedule), schedule.getExeccode(), 240_000);
            return ResultVoUtil.success("成功", ret);
        } catch (Exception e) {
            return ResultVoUtil.error(e.getMessage());
        }
    }

    /**
     * 跳转到详细页面
     */
    @GetMapping("/detail/{id}")
    @RequiresPermissions("system:schedule:detail")
    public String toDetail(@PathVariable("id") Schedule schedule, Model model) {
        model.addAttribute("schedule", schedule);
        return "/system/schedule/detail";
    }

    /**
     * 设置一条或者多条数据的状态
     */
    @RequestMapping("/status/{param}")
    @RequiresPermissions("system:schedule:status")
    @ResponseBody
    public ResultVo status(
            @PathVariable("param") String param,
            @RequestParam(value = "ids", required = false) List<Long> ids) {
        // 更新状态
        StatusEnum statusEnum = StatusUtil.getStatusEnum(param);
        if (scheduleService.updateStatus(statusEnum, ids)) {
            return ResultVoUtil.success(statusEnum.getMessage() + "成功");
        } else {
            return ResultVoUtil.error(statusEnum.getMessage() + "失败，请重新操作");
        }
    }

    /**
     * 初始一些自动完成，以及方法解析。用于页面上可以语法提示。
     *
     * @return
     */
    @RequestMapping("/ide/init")
    @ResponseBody
    public InitCompletionTypesResponse initCompletionTypes() {
        InitCompletionTypesResponse result = new InitCompletionTypesResponse();
        Map<String, List<MethodVo>> clazzs = new LinkedHashMap<>();
        Map<String, String> variables = new HashMap<>();//存放规则中的变量，如：gameId，以及Service的实例名
        List<MethodVo> functions = new ArrayList<>();//内置的方法信息
        Map<String, String> syntax = new HashMap<>();//快速生成语法

        result.setClazzs(clazzs);//类定义
        result.setVariables(variables);//内置变量
        result.setFunctions(functions);//内置方法
        result.setSyntax(syntax);//快捷语法

        // 将QL容器中指定的包下面的类，加载为内置变量，方便代码提示
        innerVariables(clazzs, variables);
        initRuleVariables(clazzs, variables);
        // 一些内置方法
        innerMethods(clazzs, functions);

        // 增加一些快捷语法块的快捷方式
        syntax.put("fori", "for(int ${1:i}=0;${1:i}<10;${1:i}++){\n\t\n}");
        syntax.put("for", "for(${1}){\n\t\n}");
        syntax.put("if", "if(${1:condition}){\n\n}");
        syntax.put("ifelse", "if(${1:condition}){\n\t\n}else{\n\t\n}");
        syntax.put("import", "import ");
        syntax.put("continue", "continue;");
        syntax.put("break", "break;");
        syntax.put("function", "function ${1:funcName}(${2:arg}){\n\t\n}");
        return result;
    }

    /**
     * 查找指定类的信息
     *
     * @return
     */
    @PostMapping("/ide/clazz")
    @ResponseBody
    public GetClassCompletionMethodsResponse getClassCompletionMethodsResponse(@RequestBody GetClassReq req) {
        try {
            String clazz = req.getClazz();
            //按照链式方式，一层层找
            Class cls = Class.forName(clazz);
            Map<String, List<MethodVo>> methods = new LinkedHashMap<>();
            methods.put(clazz, buildMethod(cls));
            return new GetClassCompletionMethodsResponse().setClassMethods(methods);
        } catch (Throwable e) {
        }
        return new GetClassCompletionMethodsResponse();
    }


    /**
     * 初始化内置方法
     *
     * @param clazzs
     * @param functions
     */
    private void innerMethods(Map<String, List<MethodVo>> clazzs, List<MethodVo> functions) {
        for (MethodVo methodVo : RuleComponent.getFunctions()) {
            functions.add(methodVo);
            buildClazz(clazzs, methodVo.getResultTypeClass());
        }
    }

    /**
     * 加载内置变量
     *
     * @param clazzs
     * @param variables
     */
    private void innerVariables(Map<String, List<MethodVo>> clazzs, Map<String, String> variables) {
        try {
            //目前没有需要扫描并加载到变量中的信息，先不扫描处理了。
//            for (String pckName : RuleComponent.getRootPackageNames()) {
//                if (pckName.equals("java.lang") || pckName.equals("java.util") || pckName.startsWith("java.lang.") || pckName.startsWith("java.util.")) {
//                    continue;//基础包单独处理
//                }
//                Set<Class<?>> clss = ClassUtil.scanPackage(pckName);
//                log.info("开始扫描并加载package中的类：" + pckName + "; 找到类数量：" + clss.size());
//                if (clss == null || clss.isEmpty()) {
//                    continue;
//                }
//                for (Class<?> cls : clss) {
//                    String name = cls.getName();
//                    if (name.contains("$") || !name.startsWith("com.linln.")) {
//                        log.info("不处理的类：" + name);
//                        continue;//内部类或多余的包不操作
//                    }
//                    if (variables.containsKey(cls.getSimpleName())) {
//                        log.info("已经存在且不处理的类：" + name);
//                        continue;//已经存在的不要加载了。但这里有一个小问题：不同包下，可能有同样类名，这时候就只能保留一个了
//                    }
//                    variables.put(cls.getSimpleName(), name);
//                    buildClazz(clazzs, cls);
//                }
//            }
            //常用的一些类扫描一下
            for (Class<?> cls : new Class<?>[]{JSON.class, JSONObject.class, JSONArray.class, HttpUtil.class, HttpRequest.class, HttpResponse.class}) {
                variables.put(cls.getSimpleName(), cls.getName());
                buildClazz(clazzs, cls);
            }
            //上面已经有包扫描了，为什么还要用下面这样写呢？因为在服务器上，上面的扫描代码无法扫描到下面这两个包里面的类
            if (RuleComponent.getRootPackageNames().contains("java.lang")) {
                //把常用的java.lang包下的类放进去
                for (Class<?> cls : new Class<?>[]{boolean.class, byte.class, char.class, double.class, float.class, int.class, long.class, short.class
                        , Boolean.class, Byte.class, Character.class, Double.class, Float.class, Integer.class, Long.class, Short.class
                        , Number.class, Math.class, Exception.class, String.class, StringBuilder.class, StringBuffer.class, System.class}) {
                    variables.put(cls.getSimpleName(), cls.getName());
                    buildClazz(clazzs, cls);
                }
            }
            if (RuleComponent.getRootPackageNames().contains("java.util")) {
                //把常用的java.util包下的类放进去
                for (Class<?> cls : new Class<?>[]{List.class, ArrayList.class, Arrays.class, Calendar.class, Collection.class, Collections.class, Date.class
                        , HashMap.class, HashSet.class, Hashtable.class, Iterator.class, LinkedHashMap.class, LinkedHashSet.class, LinkedList.class, Map.class, Objects.class, Optional.class
                        , Properties.class, Queue.class, Random.class, Set.class, SortedMap.class, SortedSet.class, Stack.class, StringJoiner.class, StringTokenizer.class, TreeMap.class, TreeSet.class
                        , UUID.class, Vector.class}) {
                    variables.put(cls.getSimpleName(), cls.getName());
                    buildClazz(clazzs, cls);
                }
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }

        //一些公共的变量(就是context里面的一些)
        variables.put("log", log.getClass().getName());
        buildClazz(clazzs, log.getClass());

    }

    /**
     * 不同的业务规则，会有不同的业务相关的变量
     *
     * @param clazzs
     * @param variables
     */
    private void initRuleVariables(Map<String, List<MethodVo>> clazzs, Map<String, String> variables) {
        variables.put("schedule", Schedule.class.getName());
        buildClazz(clazzs, Schedule.class);
    }

    /**
     * 构建class及对应的方法信息
     *
     * @param clazzs
     * @param clazz
     */
    private void buildClazz(Map<String, List<MethodVo>> clazzs, Class clazz) {
        if (clazzs.get(clazz.getName()) != null || clazz.getName().indexOf("$") != -1) {
            return;//如果类已经存在，或者是内部类，则不处理。
        }
        List<MethodVo> ms = buildMethod(clazz);
        clazzs.put(clazz.getName(), ms);
    }

    public List<MethodVo> buildMethod(Class clazz) {
        List<MethodVo> methodVos = new ArrayList<>();
        //成员变量
        for (Field field : clazz.getFields()) {
            methodVos.add(new MethodVo()
                    .setType("field")
                    .setVarName(field.getName())
                    .setResultType(field.getType().getName())
                    .setDesc(field.getType().getName())
                    .setDesc(""));
        }
        //方法
        for (Method method : clazz.getMethods()) {
            boolean isStatic = Modifier.isStatic(method.getModifiers());
            String paramTypes = Stream.of(method.getParameters()).map(item -> item.getType().getName()).collect(Collectors.joining(","));
            methodVos.add(new MethodVo()
                    .setType(isStatic ? "static" : "public")
                    .setVarName(method.getName())
                    .setParams(getMethodParameterNames(method))
                    .setResultType(method.getReturnType().getName())
                    .setDesc(method.getName() + "(" + paramTypes + ")\n" + ""));//方法注释
        }
        return methodVos;
    }

    /**
     * 获取指定方法中的参数名称
     *
     * @param method
     * @return
     */
    public static String getMethodParameterNames(final Method method) {
        LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
        String[] params = u.getParameterNames(method);
        if (params == null || params.length < 1) {
            //如果参数为空，则直接再获取一下
            if (method.getParameters() == null || method.getParameters().length < 1) {
                return "";//真的没有参数
            }
            List<String> ps = new ArrayList<>();
            for (Parameter parameter : method.getParameters()) {
                ps.add(parameter.getName());
            }
            params = ps.toArray(new String[0]);
        }
        return params == null || params.length < 1 ? "" : Arrays.stream(params).collect(Collectors.joining(", "));
    }

    @Data
    @Accessors(chain = true)
    public static class InitCompletionTypesResponse implements Serializable {
        @ApiModelProperty(value = "java类描述，即：java类名及对应的方法信息")
        private Map<String, List<MethodVo>> clazzs;

        @ApiModelProperty(value = "内置变量定义，即：变量名称以及对应的类名")
        private Map<String, String> variables;

        @ApiModelProperty(value = "内置方法，即：方法名称，以及对应的方法详细描述")
        private List<MethodVo> functions;

        @ApiModelProperty(value = "内嵌快捷语法")
        private Map<String, String> syntax;
    }

    @Data
    public static class GetClassReq implements Serializable {
        private String clazz;
    }

    @Data
    @Accessors(chain = true)
    public static class GetClassCompletionMethodsResponse implements Serializable {

        @ApiModelProperty(value = "java类描述，即：java类名及对应的方法信息")
        private Map<String, List<MethodVo>> classMethods;
    }

    @Data
    @Accessors(chain = true)
    public static class MethodVo implements Serializable {
        /**
         * 类型
         * static,public,field
         */
        private String type;
        /**
         * 变量名，用于替换和匹配输入项
         */
        private String varName;
        /**
         * 返回类型，用于匹配子对象方法
         */
        private String resultType;
        /**
         * 返回类型的Class
         */
        private Class<?> resultTypeClass;
        /**
         * 参数，用于提示项
         */
        private String params;
        /**
         * 方法注释
         */
        private String desc;
    }
}